﻿
using System;
using System.Collections.Generic;
using System.Reflection;

namespace GNUGo.Net.CLI.CliControls {
    public class CommandLineParser {
        public void ParseAndPopulate(string[] args, object options) {
            SetOptionsToDefault(options);
            for (int i = 0; i < args.Length; i++) {
                if (args[i].StartsWith("--")) {
                    string arg = args[i].Substring("--".Length);
                    int x = arg.IndexOf('=');
                    string argName;
                    string argValue;
                    if (x < 0) {
                        argName = arg;
                        argValue = null;
                    }
                    else {
                        argName = arg.Substring(0, x);
                        argValue = arg.Substring(x + 1);
                    }
                    if (!SetLongOption(options, argName, argValue)) {
                        throw new Exception(string.Format("Invalid command line argument '{0}'", args[i]));
                    }
                }
                else if (args[i].StartsWith("-")) {
                    string argName = args[i].Substring("-".Length);
                    i++;
                    string argValue = args[i];
                    if (!SetShortOption(options, argName, argValue)) {
                        throw new Exception(string.Format("Invalid command line argument '{0}'", args[i - 1]));
                    }
                }
                else {
                    throw new Exception(string.Format("Invalid command line argument '{0}'", args[i]));
                }
            }
        }

        private void SetOptionsToDefault(object options) {
            foreach (PropertyInfo propertyInfo in GetOptionProperties(options)) {
                OptionAttribute optionAttribute = GetOptionAttribute(propertyInfo);
                if (optionAttribute.DefaultValue != null) {
                    propertyInfo.SetValue(options, optionAttribute.DefaultValue, null);
                }
            }
        }

        private bool SetLongOption(object options, string name, string value) {
            foreach (PropertyInfo propertyInfo in GetOptionProperties(options)) {
                OptionAttribute optionAttribute = GetOptionAttribute(propertyInfo);
                if (optionAttribute.LongName == name) {
                    SetOption(options, propertyInfo, value);
                    return true;
                }
            }
            return false;
        }

        private bool SetShortOption(object options, string name, string value) {
            foreach (PropertyInfo propertyInfo in GetOptionProperties(options)) {
                OptionAttribute optionAttribute = GetOptionAttribute(propertyInfo);
                if (optionAttribute.ShortName == name) {
                    SetOption(options, propertyInfo, value);
                    return true;
                }
            }
            return false;
        }

        private void SetOption(object options, PropertyInfo propertyInfo, string value) {
            if (propertyInfo.PropertyType == typeof(bool)) {
                propertyInfo.SetValue(options, true, null);
            }
            else {
                propertyInfo.SetValue(options, value, null);
            }
        }

        private IEnumerable<PropertyInfo> GetOptionProperties(object options) {
            foreach (PropertyInfo propertyInfo in options.GetType().GetProperties()) {
                if (GetOptionAttribute(propertyInfo) != null) {
                    yield return propertyInfo;
                }
            }
        }

        private OptionAttribute GetOptionAttribute(ICustomAttributeProvider propertyInfo) {
            object[] attributes = propertyInfo.GetCustomAttributes(typeof(OptionAttribute), true);
            if (attributes.Length > 0) {
                return (OptionAttribute)attributes[0];
            }
            return null;
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class OptionAttribute : Attribute {
        private readonly string _longName;
        private readonly string _shortName;

        public OptionAttribute(string longName, string shortName) {
            _longName = longName;
            _shortName = shortName;
        }

        public string LongName {
            get { return _longName; }
        }

        public string ShortName {
            get { return _shortName; }
        }

        public object DefaultValue { get; set; }
    }
}
